from collections import defaultdict
import os
from pathlib import Path
import subprocess

# Slice Type Definitions
SLICE_TYPE_COPYRIGHT = "_copyright"
SLICE_TYPE_CONFIG = "_config"
SLICE_TYPE_BINS = "_bins"
SLICE_TYPE_LIBS = "_libs"


def _get_file_type_desc(filepath: str) -> str:
    """
    Uses `file -L` to get the canonical type of a file.
    Returns a lower-cased description string.
    """
    if not os.path.exists(filepath):
        return ""
    try:
        file_proc = subprocess.run(
            ["file", "-L", filepath],
            capture_output=True,
            text=True,
            check=True,
            encoding="utf-8",
            errors="ignore",
        )
        return file_proc.stdout.lower()
    except FileNotFoundError as e:
        raise RuntimeError(
            "FATAL: `file` command not found. Please ensure the `file` package is installed."
        ) from e
    except subprocess.CalledProcessError as e:
        raise RuntimeError(
            f"`file` command failed on {filepath}: {e.stderr.strip()}. Halting analysis."
        ) from e


def _is_copyright(filepath: str, pkg_root_dir: str) -> bool:
    """
    Checks if a file is a copyright/license file.
    This includes standard paths and special cases found in doc directories.
    """
    # Standard prefix-based check
    if filepath.startswith("/usr/share/licenses/"):
        return True

    # Case-insensitive check for common license filenames
    filename = Path(filepath).name.lower()
    if any(keyword in filename for keyword in ("license", "copying", "copyright", "notice")):
        # If the filename itself suggests it's a license, it's a strong indicator.
        # This will catch cases like '/usr/share/doc/c-ares/LICENSE.md'
        return True

    return False


def _is_config(filepath: str, pkg_root_dir: str) -> bool:
    if filepath.startswith("/etc/ima/"):
        return False
    return filepath.startswith("/etc/")


def _is_bin(filepath: str, pkg_root_dir: str) -> bool:
    """
    Checks if a file is a binary executable.
    """
    if not (filepath.startswith(("/usr/bin/", "/usr/sbin/", "/usr/libexec/"))):
        return False

    full_path = os.path.join(pkg_root_dir, filepath.lstrip("/"))
    desc = _get_file_type_desc(full_path)
    return "elf" in desc and "executable" in desc


def _is_library(filepath: str, pkg_root_dir: str) -> bool:
    """Checks if a file is a shared library."""
    if not (filepath.startswith("/usr/lib") or filepath.startswith("/lib")):
        return False

    full_path = os.path.join(pkg_root_dir, filepath.lstrip("/"))
    desc = _get_file_type_desc(full_path)
    return "elf" in desc and "shared object" in desc


def _get_pkg_files(pkg_root_dir: str) -> list[str]:
    files = []
    for root, _, filenames in os.walk(pkg_root_dir):
        for filename in filenames:
            full_path = os.path.join(root, filename)
            rel_path = os.path.relpath(full_path, pkg_root_dir)
            files.append(f"/{rel_path}")
    return files


# Rule Definition
CLASSIFICATION_RULES = [
    (SLICE_TYPE_COPYRIGHT, _is_copyright),
    (SLICE_TYPE_CONFIG, _is_config),
    (SLICE_TYPE_BINS, _is_bin),
    (SLICE_TYPE_LIBS, _is_library),
]


def classify_files(package_name: str, pkg_root_dir: str) -> dict[str, set[str]]:
    """
    Classifies a list of files into slices based on a defined set of rules.

    Args:
        package_name: The name of the package.
        pkg_root_dir: The root path where files were extracted.

    Returns:
        A dictionary mapping slice names to a set of file paths.
    """
    files = _get_pkg_files(pkg_root_dir)
    classified_slices = defaultdict(set)

    for filepath in files:

        slice_type_suffix = None

        for suffix, checker_func in CLASSIFICATION_RULES:
            if checker_func(filepath, pkg_root_dir):
                slice_type_suffix = suffix
                break

        # If no rule matched, skip the file
        if slice_type_suffix is None:
            continue

        slice_name = f"{package_name}{slice_type_suffix}"
        classified_slices[slice_name].add(os.path.normpath(filepath))

    return classified_slices
